﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Web.Administration;
using System.Linq;
using System.Diagnostics;
using System.IO;
using WebSvrUpdaterCore.Models;
using WebSvrUpdaterCore.Exceptions;
using System.Net;
using System.Threading;
using NLog;
namespace WebSvrUpdaterCore.IISAdmin
{
    public static class IISHelper
    {
        private static Logger _logger = LogManager.GetCurrentClassLogger();
        
        //默认程序池，不删除
        private static string[] _defaultAppPoolNames = new string[] { ".NET v2.0", ".NET v2.0 Classic", "ASP.NET v4.0", "ASP.NET v4.0 Classic" };
        public static ServerManager IISServerManager
        {
            get
            {
                return new ServerManager(); ;
            }
        }
        public static List<Site> GetAllSite()
        {
            return new ServerManager().Sites.ToList();
          
        }

        public static Site GetSiteById(long id)
        {
            var allSite = GetAllSite();
            return allSite.FirstOrDefault(w => w.Id == id);
        }

        public static Site GetSiteByName(string name)
        {
            var allSite = GetAllSite();
            return allSite.FirstOrDefault(w => w.Name == name);
        }

        /// <summary>
        /// 检查端口是否可用
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public static bool CheckPort(string ipAddress,int port)
        {
            if(port < 1 || port > 65535)
            {
                return false;
            }

            System.Net.Sockets.Socket listenSocket = null;
            try
            {
                IPAddress ip = null;
                if(string.IsNullOrEmpty(ipAddress) || "*" == ipAddress || "0.0.0.0" == ipAddress)
                {
                    ip = IPAddress.Any;
                }
                else
                {
                   if(!IPAddress.TryParse(ipAddress,out ip))
                    {
                        ip = IPAddress.Any;
                    }
                }
                IPEndPoint LocalIp = new IPEndPoint(ip, port);
                listenSocket = new System.Net.Sockets.Socket(LocalIp.AddressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
                listenSocket.Bind(LocalIp);          
            }
            catch
            {
                return false;
            }
            finally
            {
                if (listenSocket != null)
                    listenSocket.Close();
            }
            return true;
        }
        public static Site CreateSite(SiteModel siteModel)
        {
            if(siteModel == null || string.IsNullOrEmpty(siteModel.SiteName))
            {
                throw new ArgumentNullException("参数siteModel或SiteName不允许为空");
            }

            var poolName = siteModel.AppPoolName;
            if (string.IsNullOrEmpty(poolName))
            {
                poolName = siteModel.SiteName;
            }
            var pool = IISHelper.GetAppPoolByName(poolName);
            if(pool == null)
            {
               pool =  CreateAppPool(new AppPoolModel
                {
                    Name = poolName,
                    Identity = new AppPoolIdentity()
                });
                Thread.Sleep(20);

                if (pool == null)
                {
                    throw new IISManageException("创建程序池失败");
                }
                WaitForAppPoolStatusResolve(pool);
            }
            Site site = IISHelper.GetSiteByName(siteModel.SiteName);
            if (site != null)
            {
                throw new IISManageException($"创建站点失败，站点名称为'{siteModel.SiteName}'已存在");
            }
            try
            {
                
                var svrMgr = new ServerManager();
                site = svrMgr.Sites.CreateElement();
                // Initialize the new sites physical path. This is only touched during creation
                site.Applications.Add("/", string.Empty);

                // 初始化新站点默认设置
                SetToDefaults(site, svrMgr.SiteDefaults);

                // Initialize site Id by obtaining the first available
                site.Id = FirstAvailableId();

                // Set site settings to those provided
                SetSite(site, siteModel);


                //添加站点并提交更改
                svrMgr.Sites.Add(site);
                svrMgr.CommitChanges();

                //刷新
                site = GetSiteById(site.Id);
                WaitForSiteStatusResolve(site);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "创建站点发生错误");
                throw ex;
            }
            
            return site;
        }

        private static void WaitForSiteStatusResolve(Site site)
        {
            // Delay to get proper status of newly created site
            int n = 10;
            for (int i = 0; i < n; i++)
            {
                try
                {
                    Utils.StatusExtensions.FromObjectState(site.State);
                    break;
                }
                catch (System.Runtime.InteropServices.COMException)
                {
                    if (i < n - 1)
                    {
                        Thread.Sleep(10 / n);
                    }
                }
            }
        }

        private static void WaitForAppPoolStatusResolve(ApplicationPool pool)
        {
            // Delay to get proper status of newly created site
            int n = 10;
            for (int i = 0; i < n; i++)
            {
                try
                {
                    Utils.StatusExtensions.FromObjectState(pool.State);
                    break;
                }
                catch (System.Runtime.InteropServices.COMException)
                {
                    if (i < n - 1)
                    {
                        Thread.Sleep(10 / n);
                    }
                }
            }
        }
        /// <summary>
        /// 删除站点
        /// </summary>
        /// <param name="site">站点对象</param>
        /// <param name="removeAppPool">是否删除程序池</param>
        public static void DeleteSite(Site site,bool removeAppPool = false)
        {
            var poolName = site.Applications["/"].ApplicationPoolName;
            new ServerManager().Sites.Remove(site);
            var applications = GetAllSite().Where(w=> w.Id != site.Id).SelectMany(s => s.Applications);
            bool canDelAppPool = true;
            //如果程序池被其他站点使用，则不能删除该程序池
            if(applications != null)
            {
                foreach(var app in applications)
                {
                    if(app.ApplicationPoolName == poolName)
                    {
                        canDelAppPool = false;
                        break;
                    }
                }
            }
            if (canDelAppPool)
            {
                DeleteAppPool(poolName);
            }
        }

        public static Site UpdateSite(SiteModel siteModel)
        {
            if (siteModel == null)
            {
                throw new ArgumentException("siteModel");
            }

            // Obtain target site via its id number
            Site site = GetSiteById(siteModel.Id);

            // Update state of site to those specified in the model
            if (site != null)
            {
                SetSite(site, siteModel);
            }

            return site;

        }


        private static long FirstAvailableId()
        {
            ServerManager sm = new ServerManager();
            for (long id = 1; id <= long.MaxValue; id++)
            {
                if (!sm.Sites.Any(site => site.Id == id))
                {
                    return id;
                }
            }
            throw new Exception("No available Id");
        }

        private static Site SetToDefaults(Site site, SiteDefaults defaults)
        {
            site.ServerAutoStart = defaults.ServerAutoStart;

            // Limits
            site.Limits.ConnectionTimeout = defaults.Limits.ConnectionTimeout;
            site.Limits.MaxBandwidth = defaults.Limits.MaxBandwidth;
            site.Limits.MaxConnections = defaults.Limits.MaxConnections;
            site.Limits.MaxUrlSegments = defaults.Limits.MaxUrlSegments;

            // TraceFailedRequestLogging
            site.TraceFailedRequestsLogging.Enabled = defaults.TraceFailedRequestsLogging.Enabled;
            site.TraceFailedRequestsLogging.Directory = defaults.TraceFailedRequestsLogging.Directory;
            site.TraceFailedRequestsLogging.MaxLogFiles = defaults.TraceFailedRequestsLogging.MaxLogFiles;

            return site;
        }

        private static Site SetSite(Site site, SiteModel siteModel)
        {
            Debug.Assert(site != null);

            //
            // Name
            if (!string.IsNullOrEmpty(siteModel.SiteName))
            {
                site.Name = siteModel.SiteName;
            }

            //
            // Server Auto Start
            site.ServerAutoStart = siteModel.AutoStart;


            //
            // Physical Path

            var rootApp = site.Applications["/"];

            if (!string.IsNullOrEmpty(siteModel.PhysicalPath))
            {

                var physicalPath = siteModel.PhysicalPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
                var expanded = System.Environment.ExpandEnvironmentVariables(physicalPath);

                if (!PathUtil.IsFullPath(expanded))
                {
                    throw new ArgumentException("physical_path参数错误");
                }

                try
                {
                    //文件夹权限读写测试
                    var testFile = Path.Combine(physicalPath, "test");
                    using (var fileTest = File.Create(testFile)) { };
                    File.Delete(testFile);

                }
                catch (Exception)
                {
                    throw;
                }

                if (!Directory.Exists(expanded))
                {
                    //throw new DirectoryNotFoundException("PhysicalPath不存在");
                    Directory.CreateDirectory(expanded);
                }

                if (rootApp != null)
                {

                    var rootVDir = rootApp.VirtualDirectories["/"];

                    if (rootVDir != null)
                    {

                        rootVDir.PhysicalPath = physicalPath;
                    }
                }
            }
            else
            {
                throw new ArgumentNullException("PhysicalPath不能为空");
            }

            //
            // Enabled Protocols
            string enabledProtocols = siteModel.EnableHttps ? "https" : "http";
            rootApp = site.Applications["/"];
            if (rootApp != null)
            {
                rootApp.EnabledProtocols = enabledProtocols;
            }


            //
            // Limits
            if (siteModel.Limits != null)
            {
               
                site.Limits.MaxBandwidth = siteModel.Limits.MaxBandwidth;
                site.Limits.MaxConnections = siteModel.Limits.MaxConnections;
                site.Limits.MaxUrlSegments = siteModel.Limits.MaxUrlSegments;
                site.Limits.ConnectionTimeout = TimeSpan.FromSeconds(siteModel.Limits.ConnectionTimeout);
            }
            
            //
            // Bindings
            if (siteModel.Bindings != null)
            {
                
                var newBindings = new List<Binding>();
                var oldBindings = site.Bindings;
                IPAddress ipAddress = null;
                int port = 8100;
                string hostname = "";
                foreach (var b in siteModel.Bindings)
                {
                    var findObj = oldBindings.FirstOrDefault(w => w.Host.Equals(b.Host, StringComparison.OrdinalIgnoreCase) && w.Protocol.Equals(b.Protocol, StringComparison.OrdinalIgnoreCase) && w.EndPoint.Port.Equals(b.Port) && w.EndPoint.Address.ToString().Equals(b.IpAddress));
                    //没找到则加入
                    if (findObj == null)
                    {
                        port = b.Port;
                        hostname = b.Host ?? string.Empty;
                        if (b.IpAddress == "*" || b.IpAddress == string.Empty || b.IpAddress == "0.0.0.0")
                        {
                            ipAddress = IPAddress.Any;
                        }
                        else
                        {
                            ipAddress = IPAddress.Parse(b.IpAddress);
                        }

                        Binding binding = site.Bindings.CreateElement();
                        var ipModel = ipAddress.Equals(IPAddress.Any) ? "*" : ipAddress.ToString();
                        binding.BindingInformation = $"{ipModel}:{port}:{hostname}";
                        
                        binding.Protocol = "https".Equals(b.Protocol,StringComparison.OrdinalIgnoreCase) ? "https":"http";
                        if ("http".Equals(b.Protocol, StringComparison.OrdinalIgnoreCase))
                        {
                            binding.SslFlags = SslFlags.None;
                        }
                        else
                        {
                            binding.SslFlags = (SslFlags)b.SslFlags;
                            binding.CertificateHash = b.CertificateHash;
                            binding.CertificateStoreName = b.CertificateStoreName;
                        }
                        binding.UseDsMapper = b.UseDsMapper;
                        newBindings.Add(binding);
                    }
                }
                // All bindings have been verified and added to the list
                // Clear the old list, and add the new
                
                newBindings.ForEach(binding => site.Bindings.Add(binding));
            }

            //
            // App Pool
            var poolName = siteModel.AppPoolName;
            if (string.IsNullOrEmpty(poolName))
            {
                poolName = siteModel.SiteName;
            }
            rootApp = site.Applications["/"];

            if (rootApp == null)
            {
                throw new ArgumentException("application_pool", "Root application does not exist.");
            }
            var pool = GetAppPoolByName(poolName);
            if (pool == null)
            {
                throw new ArgumentException("名称为：{0}的程序池不存在,请先创建", poolName);
                ////创建程序池
                //pool = CreateAppPool(new AppPoolModel {
                //    Name = poolName,
                //    Identity = new AppPoolIdentity()
                //});

            }
            // REVIEW: Should we create the root application if it doesn't exist and they specify an application pool?
            // We decided not to do this for physical_path.
            // Application pool for a site is extracted from the site's root application
            rootApp.ApplicationPoolName = poolName;
            return site;
        }


        //ApplicationPool
        /// <summary>
        /// 查询所有程序池
        /// </summary>
        /// <returns></returns>
        public static List<ApplicationPool> GetAllAppPools()
        {
            return new ServerManager().ApplicationPools.ToList();

        }

        /// <summary>
        /// 通过名称获取程序池对象
        /// </summary>
        /// <param name="name">程序池名称</param>
        /// <returns></returns>
        public static ApplicationPool GetAppPoolByName(string name)
        {
            return GetAllAppPools().FirstOrDefault(w => w.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// 新增程序池
        /// </summary>
        /// <param name="poolModel"></param>
        /// <returns></returns>
        public static ApplicationPool CreateAppPool(AppPoolModel poolModel)
        {
            var pool = GetAppPoolByName(poolModel.Name);
            if(pool == null)
            {
                ServerManager svrMgr  = new ServerManager();
                
                ApplicationPool appPool = svrMgr.ApplicationPools.CreateElement();
                SetToDefaults(appPool, svrMgr.ApplicationPoolDefaults);
                SetAppPool(appPool, poolModel,svrMgr);
                svrMgr.ApplicationPools.Add(appPool);
                svrMgr.CommitChanges();
                
                pool = GetAppPoolByName(poolModel.Name);
            }

            return pool;
        }

        /// <summary>
        /// 删除程序池
        /// </summary>
        /// <param name="poolName">程序池名称</param>
        public static void DeleteAppPool(string poolName)
        {
            var skipPool = _defaultAppPoolNames.FirstOrDefault(w => w.Equals(poolName, StringComparison.OrdinalIgnoreCase));
            if (skipPool != null) return;
            var pool = GetAllAppPools().FirstOrDefault(w => w.Name.Equals(poolName, StringComparison.OrdinalIgnoreCase));
            if (pool == null) return;
            
            var svrMgr = new ServerManager();
            svrMgr.ApplicationPools.Remove(pool);
            svrMgr.CommitChanges();
        }

        /// <summary>
        /// 修改程序池
        /// </summary>
        /// <param name="poolName"></param>
        /// <param name="poolModel"></param>
        /// <returns></returns>
        public static ApplicationPool UpdateAppPool(string poolName, AppPoolModel poolModel)
        {
            var pool = GetAllAppPools().FirstOrDefault(w => w.Name.Equals(poolName, StringComparison.OrdinalIgnoreCase));
            if(pool != null)
            {
                var svrMgr = new ServerManager();
                SetAppPool(pool, poolModel,svrMgr);
                svrMgr.CommitChanges();

            }
            return pool;
        }
        private static void SetToDefaults(ApplicationPool pool, ApplicationPoolDefaults defaults)
        {
            pool.ManagedPipelineMode = defaults.ManagedPipelineMode;
            pool.ManagedRuntimeVersion = defaults.ManagedRuntimeVersion;
            pool.Enable32BitAppOnWin64 = defaults.Enable32BitAppOnWin64;
            pool.QueueLength = defaults.QueueLength;
            pool.AutoStart = defaults.AutoStart;

            pool.StartMode = defaults.StartMode;

            pool.Cpu.Limit = defaults.Cpu.Limit;
            pool.Cpu.ResetInterval = defaults.Cpu.ResetInterval;
            pool.Cpu.Action = defaults.Cpu.Action;
            pool.Cpu.SmpAffinitized = defaults.Cpu.SmpAffinitized;
            pool.Cpu.SmpProcessorAffinityMask = defaults.Cpu.SmpProcessorAffinityMask;
            pool.Cpu.SmpProcessorAffinityMask2 = defaults.Cpu.SmpProcessorAffinityMask2;

            pool.Failure.AutoShutdownExe = defaults.Failure.AutoShutdownExe;
            pool.Failure.AutoShutdownParams = defaults.Failure.AutoShutdownParams;
            pool.Failure.LoadBalancerCapabilities = defaults.Failure.LoadBalancerCapabilities;
            pool.Failure.RapidFailProtection = defaults.Failure.RapidFailProtection;

            if (pool.ProcessModel.Schema.HasAttribute("idleTimeoutAction"))
            {
                pool.ProcessModel.IdleTimeoutAction = defaults.ProcessModel.IdleTimeoutAction;
            }

            pool.ProcessModel.MaxProcesses = defaults.ProcessModel.MaxProcesses;
            pool.ProcessModel.PingingEnabled = defaults.ProcessModel.PingingEnabled;
            pool.ProcessModel.IdleTimeout = defaults.ProcessModel.IdleTimeout;
            pool.ProcessModel.PingInterval = defaults.ProcessModel.PingInterval;
            pool.ProcessModel.PingResponseTime = defaults.ProcessModel.PingResponseTime;
            pool.ProcessModel.ShutdownTimeLimit = defaults.ProcessModel.ShutdownTimeLimit;
            pool.ProcessModel.StartupTimeLimit = defaults.ProcessModel.StartupTimeLimit;

            pool.Recycling.LogEventOnRecycle = defaults.Recycling.LogEventOnRecycle;
            pool.Recycling.DisallowOverlappingRotation = defaults.Recycling.DisallowOverlappingRotation;
            pool.Recycling.DisallowRotationOnConfigChange = defaults.Recycling.DisallowRotationOnConfigChange;
            pool.Recycling.PeriodicRestart.PrivateMemory = defaults.Recycling.PeriodicRestart.PrivateMemory;
            pool.Recycling.PeriodicRestart.Memory = defaults.Recycling.PeriodicRestart.Memory;
            pool.Recycling.PeriodicRestart.Requests = defaults.Recycling.PeriodicRestart.Requests;
            pool.Recycling.PeriodicRestart.Time = defaults.Recycling.PeriodicRestart.Time;
        }

        private static void SetAppPool(ApplicationPool appPool, AppPoolModel model,  ServerManager svrMgr)
        {
            Debug.Assert(appPool != null);
            Debug.Assert((bool)(model != null));
                        
            SetName(appPool, model.Name,svrMgr);
            appPool.StartMode = (StartMode)model.StartMode;
            appPool.ManagedPipelineMode = (ManagedPipelineMode) model.ManagedPipelineMode;
            appPool.ManagedRuntimeVersion = model.ManagedRuntimeVersion;
            appPool.Enable32BitAppOnWin64 = model.Enable32BitAppOnWin64;
            appPool.QueueLength = model.QueueLength;
            appPool.AutoStart = model.AutoStart;

            // CPU
            

            // Process Model
           
            // Identity
            if (model.Identity != null)
            {
                
                appPool.ProcessModel.IdentityType = (ProcessModelIdentityType)model.Identity.IdentityType;
                appPool.ProcessModel.LoadUserProfile = model.Identity.LoadUserProfile;
                appPool.ProcessModel.UserName = model.Identity.UserName;
                appPool.ProcessModel.Password = model.Identity.Password;
            }

            // Recycling
          
            // Rapid Fail Protection
       
            // Process Orphaning
        

        }

        private static void SetName(ApplicationPool pool, string name, ServerManager svrMgr)
        {
            if(svrMgr == null  || pool == null || string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("SetName中任意参数不能为空");
            }

            const string isPresentTag = "isPresent";

            if (GetAllAppPools().Any(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && !p.Name.Equals(pool.Name, StringComparison.OrdinalIgnoreCase)))
            {
                throw new AlreadyExistsException("name");
            }

            if (pool.Name != null)
            {
                var applications = GetAllSite().SelectMany(s => s.Applications);
                bool isDefault = pool.Name.Equals(svrMgr.ApplicationDefaults.ApplicationPoolName, StringComparison.OrdinalIgnoreCase);

                if (isDefault)
                {
                    svrMgr.ApplicationDefaults.ApplicationPoolName = name;
                }

                foreach (var app in applications)
                {
                    if (app.ApplicationPoolName.Equals(pool.Name, StringComparison.OrdinalIgnoreCase) && (!isDefault || (bool)app.GetAttribute("applicationPool").GetMetadata(isPresentTag)))
                    {
                        app.ApplicationPoolName = name;
                    }
                }
            }

            pool.Name = name;
        }
    }
}
